package boardgamer.common.client.board;

import android.graphics.PointF;
import android.graphics.RectF;
import boardgamer.common.board.HexagonBoard;
import boardgamer.common.client.Vector;
import boardgamer.common.client.layout.HasLayout;
import com.google.common.collect.Ordering;
import com.google.common.collect.Table.Cell;

import static com.google.common.base.Preconditions.checkNotNull;

public class HexagonBoardLayout<T extends HasLayout, B extends HexagonBoard<T>>
    extends BoardLayout<T, B> {

  private static final float SQRT_3 = (float) Math.sqrt(3);

  private final PointF origin;
  private final PointF xDisplacement;
  private final PointF yDisplacement;

  /**
   * @param distance Distance between the centers of two hex tiles when arranged with the closest
   *                 sides parallel to each other
   * @param xDir     direction of the x axis. Direction of y axis is 120 degrees counter clockwise
   *                 rotation of the x axis around the origin
   * @param origin   coordinate of tile (0, 0)
   */
  public HexagonBoardLayout(float distance, PointF xDir, PointF origin) {
    super();
    checkNotNull(xDir, "xDir");
    this.origin = checkNotNull(origin, "origin");

    Vector normalizedX = Vector.fromPointF(xDir).unitVector();
    this.xDisplacement = Vector.copyOf(normalizedX)
        .multiply(distance)
        .asPointF();
    this.yDisplacement = new PointF(
        (-normalizedX.getX() - SQRT_3 * normalizedX.getY()) / 2 * distance,
        (SQRT_3 * normalizedX.getX() - normalizedX.getY()) / 2 * distance);
  }

  @Override
  Vector getTileScreenCoordinates(int tileX, int tileY) {
    return Vector.fromPointF(origin)
        .add(Vector.fromPointF(xDisplacement)
            .multiply(tileX))
        .add(Vector.fromPointF(yDisplacement)
            .multiply(tileY));
  }

  @Override
  public T getTile(PointF screenCoordinate) {
    checkNotNull(board, "Cannot call getTile until setBoard is called");

    final Vector pos = Vector.fromPointF(screenCoordinate);
    Cell<Integer, Integer, T> result = new Ordering<Cell<Integer, Integer, T>>() {
      @Override
      public int compare(Cell<Integer, Integer, T> left,
                         Cell<Integer, Integer, T> right) {
        return distance(left).compareTo(distance(right));
      }

      private Float distance(Cell<Integer, Integer, T> cell) {
        return getTileCenter(cell.getRowKey(), cell.getColumnKey())
            .substract(pos)
            .lengthSquared();
      }
    }.min(board.getBoard().cellSet());

    RectF resultRect = board.getTile(result.getRowKey(), result.getColumnKey()).getBoundingRect();
    if (resultRect.contains(screenCoordinate.x, screenCoordinate.y)) {
      return result.getValue();
    } else {
      return null;
    }
  }

  private Vector getTileCenter(int x, int y) {
    RectF rect = board.getTile(x, y).getBoundingRect();
    return new Vector(rect.centerX(), rect.centerY());
  }
}
